/**
 * @description lib/koa-signature
 *
 * @author xiaomi
 */
'use strict';

const semver = require('semver');
const lodash = require('lodash');
const Promise = require('bluebird');
const signature = require('./signature');
const getClientByUA = require('../utils/get-client-by-ua');

module.exports = function({
    allowedIps = [],
    allowedUrls = [],
    production = true,
    allowedApps = {},
    signatureMethod = 'api',
    timestampOffset = 5 * 60 * 1000,
    getMetaInfo = function(ctx) {
        return {
            appId: ctx.get('X-Appid'),
            signature: ctx.get('X-Signature'),
            timestamp: +ctx.get('X-Timestamp') || 0,
            client: getClientByUA(ctx.get('User-Agent'))
        };
    }
} = {}) {
    return function *(next) {
        // meta info
        let metaInfo = yield Promise.try(() => {
            return getMetaInfo(this, this.req);
        })
        .catch(err => {
            this.throw(400, err);
        });

        // white list ping++回调放行 回调内有签名验证
        if(lodash.indexOf(allowedUrls, this.path) > -1 ||
            (
                !metaInfo.signature &&
                lodash.indexOf(allowedIps, this.ip) > -1
            )
        ) {
            yield next;

            return;
        }
        // 线上环境暂时只对 PUT 和 POST 签名验证
        if(production && this.method !== 'PUT' && this.method !== 'POST') {
            yield next;
            return;
        }
        if(!metaInfo.signature) {
            this.throw(400, new Error('signature required'));
        }
        if(!metaInfo.appId) {
            this.throw(400, new Error('appId required'));
        }

        const subApp = allowedApps[metaInfo.appId];

        if(!subApp) {
            this.throw(400, new Error('appId not allowed'));
        }

        // check timestamp, unit: second
        let now = Math.floor(Date.now() / 1000);
        if(Math.abs(now - metaInfo.timestamp) > timestampOffset) {
            this.throw(400, new Error('timestamp expires'));
        }

        // build data
        let data = lodash.assign({}, metaInfo, {
            appSecret: subApp.secret,
            body: this.request.body,
            query: this.query,
            path: this.path,
            method: this.method
        });
        // 兼容 Android 1.14以下版本 1.7.35 < 版本号 < 1.14.0 客户端签名没有拼接body
        if(metaInfo.client.id === 4 && semver.gt(metaInfo.client.version, '1.7.35') && semver.lt(metaInfo.client.version, '1.14.0')) {
            data.bodyEscaped = true;
        }
        let sign = signature(data, signatureMethod);

        if(sign !== metaInfo.signature) {
            this.throw(403, new Error('signature invalid'));
        }

        yield next;
    };
};
